var fs = require('fs');
var os = require('os');
var crypto = require('crypto');
var child_process = require('child_process');
var path = require('path');
var replace = require('replace');
var _ = require('lodash');
var argv = require('yargs').argv;
var json = require('comment-json');
var chalk = require('chalk');
var mustache = require('mustache');
var switchcase = require('switchcase');
let plist = require('plist');

let localConfigPath = './localconfig.js';
try {
	var config = require(localConfigPath);
} catch (e) {
	console.log('no localconfig', e);
	config = {};
}

process.on('exit', code => {
	if (code != 0) return;
	var lines = fs
		.readFileSync(localConfigPath)
		.toString()
		.split('\n');
	// console.log('before exit', lines);
	var newlines = lines.map(v => {
		//以tool:once结尾，且不以//开头（忽略空白和tab）
		//是一次性选项，在执行结束时将之注释
		if (v.endsWith('tool:once') && /^\s*\//.test(v) == false) {
			return '//' + v;
		} else {
			return v;
		}
	});
	fs.writeFileSync(localConfigPath, newlines.join('\n'));
});

//是否在win10 wsl
var isWSL = false;
try {
	child_process.execSync('which wsl.exe');
	isWSL = true;
	// eslint-disable-next-line no-empty
} catch (e) {}

console.log('npm-cmd', process.argv, 'isWSL', isWSL);

//docker相关配置
var projectName = 'mb';
var dockerRegistry = 'cz.19v5.com:5555/wellbye/mb';
// dockerRegistry = '677024980315.dkr.ecr.ap-southeast-1.amazonaws.com';

//api image
var dockerImageName = `${projectName}-api`;
var dockerImageUrl = `${dockerRegistry}/${dockerImageName}`;
//res image
var dockerResImageName = `${projectName}-res`;
var dockerResImageUrl = `${dockerRegistry}/${dockerResImageName}`;

//生成ipa的配置
var iosSimApp = '/Applications/Xcode10.2.app/Contents/Developer/Applications/Simulator.app';
var androidSimApp = '';

var outputIPA = path.resolve(`../build/${projectName}.ipa`);
var xprjb = '../Product/Apps/IOS/Project/'; //xcode工程生成在这里，这是ksfeditor里写死的

let po = plist.parse(fs.readFileSync('../mb.iOS/Info.plist').toString());
// console.log(po);
var iosAppId = po.CFBundleIdentifier;
var iosMainProjectFile = '../mb.iOS/mb.iOS.csproj';
var iosBuildPath = path.resolve('../mb.iOS/bin/iPhone/Debug/mb.iOS.app');
if (config.iosDeviceBuild) iosBuildPath = '../mb.iOS/bin/iPhone/Debug/device-builds/ipad6.11-12.1.1/mb.iOS.app';
var iosBuildRemotePath;
if (config.vmac) {
	iosBuildRemotePath = iosBuildPath.replace('../mb.iOS', `./Library/Caches/Xamarin/mtbs/builds/mb.iOS/${config.vmacBuild}/`);
}

//生成apk的配置
var androidUseFastDeploy = false;
var androidProjectPath = '../Product/Apps/Android/release/mj';
var androidBuidPath = '../mb.Droid/bin/Debug/com.jj.mb-Signed.apk';

let pgyUserKey = '275ac31959763b5ace3389fffa5e0769';
let pgyApiKey = 'b63ffee934fcd1e5637b68e6fae5da50';
var port = 8081;
var cmd = process.argv[2];
if (!cmd) {
	console.log('please input command');
	process.exit(1);
}

const ucmdOPT = fs.existsSync('.ucmd.json') ? json.parse(fs.readFileSync('.ucmd.json')) : {};

try {
	var ssh_auth_sock = fs
		.readFileSync(process.env.HOME + '/.ssh_auth')
		.toString()
		.split('=')[1];
	process.env.SSH_AUTH_SOCK = ssh_auth_sock.trim();
} catch (e) {
	//
}
const baseEnv = {
	CODE_SIGN_IDENTITY: 'iPhone Developer',
	PROVISIONING_PROFILE: '5ad27f68-0e0f-41b6-a39c-350683afee4f',
	...process.env,
};

const sleep = async function(ms) {
	await new Promise(resolve => setTimeout(resolve, ms));
};

function desEncrypt(message, key) {
	key = key.length >= 8 ? key.slice(0, 8) : key.concat('0'.repeat(8 - key.length));
	const keyHex = Buffer.from(key);
	const cipher = crypto.createCipheriv('des-cbc', keyHex, keyHex);
	let c = cipher.update(message, 'utf8', 'base64');
	c += cipher.final('base64');
	return c;
}

function desDecrypt(text, key) {
	key = key.length >= 8 ? key.slice(0, 8) : key.concat('0'.repeat(8 - key.length));
	const keyHex = Buffer.from(key);
	const cipher = crypto.createDecipheriv('des-cbc', keyHex, keyHex);
	let c = cipher.update(text, 'base64', 'utf8');
	c += cipher.final('utf8');
	return c;
}

const getLocalIP = function() {
	var os = require('os');
	var ifaces = os.networkInterfaces();
	let forceRegen = argv.f;

	var ipList = [];
	Object.keys(ifaces).forEach(function(ifname) {
		var alias = 0;

		ifaces[ifname].forEach(function(iface) {
			if ('IPv4' !== iface.family || iface.internal !== false) {
				// skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
				return;
			}
			ipList.push(iface.address);
		});
	});
	console.log('iplist', ipList);
	return ipList;
};

//注意：如果需要得到输出为返回值，必须传入opt={stdio:""}
const shell = function(cwd, cmd, opt) {
	try {
		console.log(cmd);
		var opt_env = (opt && opt.env) || {};
		var r = child_process.execSync(cmd, {
			stdio: 'inherit',
			cwd,
			// env: { ...baseEnv, ...opt_env },
			...opt,
		});
		return r ? r.toString() : undefined;
	} catch (e) {
		if (opt && opt.noerror) {
			console.log('ignore error', e.message);
		} else throw e;
	}
};

const getAutoServer = function() {
	var autoserver = config['tool-autoserver'];
	if (autoserver == 'dev') autoserver = getLocalIP()[0];
	return autoserver;
};

const fixPath = upath => {
	if (isWSL)
		return shell('.', `wslpath -aw ${upath}`, { stdio: '' })
			.trim()
			.replace(/\\/g, '\\\\');
	else return upath;
};

module.exports.startFluentd = function() {
	console.log('fluentd');
	if (argv.s) {
		shell('.', 'docker rm -f mjfluentd');
		return;
	}
	var tag = 'myfluent';
	var isDaemon = argv.d ? '-d' : '';
	shell('fluentd', `docker build . -t ${tag}`);
	shell(
		'fluentd',
		`docker run  --rm --name mjfluentd -it ${isDaemon}\\
    -p 9880:9880\\
    -v ${path.resolve('./fluentd/conf')}:/fluentd/etc\\
    -v ~/Library/Logs/Unity/Player.log:/playerlog\\
    -v ~/tmp:/logs\\
     ${tag}`
	);
};

module.exports.apk = function() {
	var isWin = /^win/.test(process.platform);
	if (!argv.debug) androidBuidPath = androidBuidPath.replace('Debug', 'Release');
	let apkUnsigned = androidBuidPath.replace('-Signed', '');

	let adktool = `${process.env.ANDROID_HOME}/build-tools/28.0.3`;
	let exe = isWSL ? '.exe' : '';
	let bat = isWSL ? '.bat' : '';
	let apksigner = `${adktool}/apksigner` + bat;
	let zipalign = `${adktool}/zipalign` + exe;
	let aapt = `${adktool}/aapt` + exe;

	if (argv.ssj) {
		//show servers.json
		if (argv.ssj == true) shell('.', `unzip -c ${apkUnsigned} assets/servers.json`);
		else {
			let apkCopy = apkUnsigned + '.copy';
			shell('.', `cp ${apkUnsigned} ${apkCopy}`);
			shell('.', `aapt add -v ${apkCopy} assets/user_info.txt`);
		}
	}

	if (argv.mount) {
		let file = argv.signed ? androidBuidPath : apkUnsigned;
		shell('.', `fuse-zip ${file} /tmp/apk`);
	}

	if (argv.vsign) {
		let file = androidBuidPath;
		if (typeof argv.vsign == 'string') file = androidBuidPath.replace('Signed', 'Signed-' + argv.vsign);
		if (argv.in) file = argv.in;
		let cmd = `${apksigner} verify --print-certs -v ${file}`;
		shell('.', cmd);
	}

	if (argv.sign) {
		//生成钥匙库的命令：
		//keytool -genkeypair -v -keystore <filename>.keystore -alias <key-name> -keyalg RSA  -keysize 2048 -validity 10000
		//在wsl下执行apksigner.bat需要
		//sudo sh -c "echo :WindowsBatch:E::bat::/init: > /proc/sys/fs/binfmt_misc/register"

		let keystore = argv.sign;
		if (!fs.existsSync(keystore)) {
			console.log('--sign 指定的keystore文件不存在', keystore);
			return;
		}
		//后缀加上钥匙文件名以便区分
		let suffix = /.*\/(.+)\.keystore/.exec(keystore)[1];

		//替换默认输入
		if (argv.in) apkUnsigned = argv.in;

		let pass = argv.keypass ? `--ks-pass pass:${argv.keypass}` : '';
		let apkSigned = apkUnsigned.replace('.apk', `-Signed-${suffix}.apk`);
		shell('.', `${zipalign} -f -v 4 ${apkUnsigned} ${apkUnsigned}.aligned >/dev/null`);
		shell('.', `${apksigner} sign --ks ${fixPath(keystore)} ${pass} --ks-key-alias mykey --out ${apkSigned} ${apkUnsigned}.aligned`);
		shell('.', `rm -rf ${apkUnsigned}.aligned`);
	}
};

module.exports.apk_flavor = function() {
	let fdir = argv.f;
	let config = require(fdir + '/config.js');
	console.log(config);
	if (!argv.debug) androidBuidPath = androidBuidPath.replace('Debug', 'Release');
	let apkUnsigned = androidBuidPath.replace('-Signed', '');
	let apkTmp = 'tmp/' + /.+\/(.+)$/.exec(apkUnsigned)[1];
	shell('.', `cp ${apkUnsigned} ${apkTmp}`);

	config.updateAssets.forEach(v => {
		/*
		先删除原文件，再添加新文件，再改名。zipnote必须是3.1版本
		zip -d tmp/com.jj.mb.apk assets/servers.json
		zip -u tmp/com.jj.mb.apk ../apkf1/servers.json
		printf "@ ../apkf1/servers.json\n@=assets/servers.json\n" | zipnote -w tmp/com.jj.mb.apk
		*/
		let fpath = `${fdir}/${v}`;
		let epath = fpath + '.e'; //加密后的文件
		let entry = epath.replace(/^\//, ''); //如果是根路径，添到zip后会去掉头部的/
		fs.writeFileSync(epath, 'mbec:' + desEncrypt(fs.readFileSync(fpath).toString(), 'xiaoting'));
		shell('.', `zip -d ${apkTmp} assets/${v}`, { noerror: true });
		shell('.', `zip -u ${apkTmp} ${epath}`);
		shell('.', `printf "@ ${entry}\\n@=assets/${v}\\n" | zipnote -w ${apkTmp}`);
		fs.unlinkSync(epath);
	});

	argv.in = apkTmp;
	argv.sign = `${fdir}/${config.sign.ks}`;
	argv.keypass = config.sign.pass;
	module.exports.apk();
};

module.exports.pgy = function() {
	if (argv.apk) {
		androidBuidPath = androidBuidPath.replace('Debug', 'Release');
		shell('./', `curl --progress-bar -F "file=@${androidBuidPath}" -F "uKey=${pgyUserKey}" -F "_api_key=${pgyApiKey}" http://www.pgyer.com/apiv1/app/upload > /tmp/uploadlog; `);
	}
	if (argv.ios) {
		// shell('.', `curl --progress-bar -F "file=@${outputIPA}" -F "uKey=${pgyUserKey}" -F "_api_key=${pgyApiKey}" http://www.pgyer.com/apiv1/app/upload > /tmp/uploadlog; `);
	}
};

var androidDevice;
var selectAndroidDevice = async function(idx) {
	var aidx = idx || config.androidDeviceIndex || 1;
	var cmd;
	if (typeof aidx == 'number') {
		cmd = `adb devices|sed -n ${aidx + 1}p|cut -f 1`;
	} else {
		cmd = `adb devices|grep ${aidx}|cut -f 1`;
	}
	while (!androidDevice) {
		androidDevice = shell('./', cmd, { stdio: '' }).trim();
		console.log('devcie is:', androidDevice);
		await sleep(1000);
	}

	let apiLevel;
	while (!apiLevel) {
		// prettier-ignore
		try{
			var r = shell( '.', `adb -s ${androidDevice} shell getprop ro.build.version.sdk | sed '2p'`, { stdio: '' });
			apiLevel = parseInt(r);
			console.log('apilevel', apiLevel);
		}
		catch(e){
			await sleep(1000);
		}
	}
	return { apiLevel };
};
module.exports.android_log = async function() {
	let cmd;
	let { apiLevel } = await selectAndroidDevice(argv.d);

	//打开调试选项，可看到异常栈行号
	shell('.', `adb -s ${androidDevice} shell setprop debug.mono.debug 1`);

	if (argv.m) {
		//只看mono输出
		//07-25 18:02:17.448 6964 7020 E mono : System.NullReferenceException: Object reference not set to an instance of an object
		cmd = `adb -s ${androidDevice} logcat *:S mono-stdout:V mono:E Mono:W MonoDroid:I AndroidRuntime:V`;
	} else {
		//看所有输出，通过关键字屏蔽杂乱输出
		var grepParams = argv._.splice(1)
			.concat(config.androidLogIgnore || [])
			.map(v => `-e "${v}"`)
			.join(' ');
		cmd = `adb -s ${androidDevice}  logcat  | grep --line-buffered -v -E -e DllImport -e java_interop -e "Assembly Ref addref" ${grepParams}`;
	}

	// 把多余的列去掉以免占空间
	if (apiLevel > 22 && os.platform() != 'win32') {
		var awk = 'awk';
		cmd += ` | ${awk} '{   printf("%s %s ",$2, $6); for(i=7;i<=NF;i++) printf("%s ",$i); printf("\\n"); system("") } ' `;
	}

	//console.writeline输出的类型都为mono-stdout，对其着色以便观察业务逻辑log，其它的多为系统log
	// cmd += ' | grep --color -E "mono-stdout|$" --line-buffered ';

	shell('.', `adb -s ${androidDevice} logcat -c `);
	shell('.', cmd);
};

module.exports.android_stop = function() {
	shell('.', `adb -s ${androidDevice} shell am force-stop com.jj.mj`);
};

module.exports.android_start = async function() {
	selectAndroidDevice(argv.d);

	if (androidDevice == '') {
		return;
	}

	if (!/(emulator)|(localhost)/.test(androidDevice)) {
		//真机强制走release
		argv.r = true;
	}

	if (argv.r) {
		androidBuidPath = androidBuidPath.replace('Debug', 'Release');
	}
	if (argv.b) {
		console.log('build android...');
		await module.exports.buildCS('android');
	}
	module.exports.android_stop();

	if (!argv.r && androidUseFastDeploy) {
		//debug模式，vs的快速部署，msbuild /t:install 可直接上传dll，不需要再手动装apk了
		console.log('found debug fast deploy, skip manually install');
	} else {
		await doIfTimeNewer(androidBuidPath, { suffix: androidDevice }, [], ['bin', 'obj'], () => {
			shell('.', `adb -s ${androidDevice} install -r ${androidBuidPath}`);
		});
	}

	var autoserver = getAutoServer();
	var ip = getLocalIP()[0];

	var args = {
		autoserver: autoserver,
		reportlog: ip + ':9283',
	};
	var ptk = 'pt-';
	for (var k in config) {
		console.log('check key', k);
		if (k.startsWith(ptk)) {
			args[k.substr(ptk.length)] = config[k];
		}
	}
	console.log('args', args);
	var aec = Buffer.from(JSON.stringify(args)).toString('base64');
	var activity_name = shell('.', `adb  -s ${androidDevice} shell pm dump com.jj.mb|grep -A 1 ".MAIN"| sed -n "2p"|tr -s " "|cut -d" " -f 3`, { stdio: '' }).trim();

	shell('.', `adb  -s ${androidDevice} shell am start -e StartParam ${aec}  -S ${activity_name}`);

	if (config.autoActiveSim) {
		if (isWSL) {
			shell('./', '/mnt/d/nircmd.exe win activate title "Ubuntu"');
			shell('./', '/mnt/d/nircmd.exe win activate ititle "Android Emulator"');
			shell('./', '/mnt/d/nircmd.exe win activate ititle "Visual Studio Code"');
			shell('./', '/mnt/d/nircmd.exe win max ititle "Visual Studio Code"');
		} else {
			shell('./', 'open /Applications/Utilities/Terminal.app');
			if (androidDevice.indexOf('emulator') >= 0) {
				await sleep(10);
				var osascript = `tell application "System Events"
							tell application process "Dock"
								tell list 1
								tell UI element "emulator"
									perform action "AXPress"
								end tell
								end tell
							end tell
						end tell`;
				shell('./', `osascript -e '${osascript}'`);
			}
			if (config.autoActiveSim == 2) {
				shell('./', 'open "/Applications/Visual Studio Code.app"');
			}
		}
	}
};

var iosDevice = null;
var selectIOSDevice = idx => {
	/*
	iPhone 7 (184A13D0-4145-4C52-A03E-579F34010F85) (Booted) 
	iPhone XS Max (82C67D6E-1546-4959-9E77-7A289109F1DB) (Booted) 
	Phone: iPhone XS Max (82C67D6E-1546-4959-9E77-7A289109F1DB) (Booted)

	最后一行是booted默认值，搜索要去掉
	*/
	var aidx = idx || config.iosDeviceIndex;
	if (aidx) {
		var cmd = `xcrun simctl list|grep -v ":"|grep Booted|grep "${aidx}"|cut -d "(" -f 1`;
		iosDevice = shell('./', cmd, { stdio: '' }).trim();
	} else {
		iosDevice = 'booted';
	}
	console.log('devcie is:', iosDevice);
};

module.exports.ios_log = function() {
	selectIOSDevice(argv.d);

	var processName = iosBuildPath.match(/.*[/\\](.+)/)[1];
	processName = processName.replace('.app', '');
	var vl = 0;
	var ssh = config.vmac && `ssh ${config.vmac} `;
	if (argv.l) vl = 1;
	if (argv.ll) vl = 2;

	if (argv.sim) {
		var prediate = `processImagePath contains '${processName}'`;

		//（尽可能）只显示自己console.write的东西。通过过滤条件来实现
		if (argv.l || argv.ll) {
			prediate += ' && (messageType == \'default\' || messageType == \'error\')';
			prediate += ' && eventType == \'logEvent\' ';
		}
		if (argv.ll) prediate += ` && senderImagePath contains '${processName}'`;

		var cmd = `xcrun simctl spawn "${iosDevice}" log stream --level debug --predicate "${prediate}" `;

		//用json显示，可观察字段名，作为过滤条件
		if (argv.j) cmd += ' --style json ';
		else {
			//显示第4列和第8列之后的
			if (argv.f) {
				//2019-03-31 16:47:59.318089+0800 0x36015d Activity 0xffa0f 37657 0 mb.iOS: (UIKitCore) send control actions
				//awk含义：检查$1是为否日期，是则为标准日志行，取2,8-N字段显示，不是则为多行消息，全行显示
				//过滤掉\r，否则在着色时导致把这一行前面的都擦除
				let bin = 'awk';
				cmd += `| ${bin} '{
							if( $1!~/[0-9]{4}-[0-9]{2}-[0-9]{2}/ ){ s=$0; sub(/\\r/," <<<",s); print s; } 
							else{ 
								printf($2);
								for(i=8;i<=NF;i++) printf(" %s",$i); 
								printf("\\n"); 
								fflush() 
							} 
						}' `;
			}

			//过滤crash时一长串没用处的c stack
			cmd += ' | grep --line-buffered -v -E -e critical';
			//grep着色关键字加|$表示实际上每一行都匹配（因为都有行尾），但如果是因为行尾才匹配上，那着色区域只有行尾相当于没效果，所以实际效果是只有关键字的才着色，但每一行都输出
			cmd += ' | grep --line-buffered --color -E "STDOUT|cs:Line|$" ';
		}
		cmd += argv._.splice(1).join(' ');

		if (ssh) {
			//命令太复杂了，各种引号转义问题，只能先存成脚本文件再重定向输入
			var sfn = '/tmp/ios-log.sh';
			fs.writeFileSync(sfn, cmd);
			cmd = `${ssh} < ${sfn}`;
			cmd += ' | grep --line-buffered --color -E "STDOUT|$" ';
		}
		shell('.', cmd);
	} else {
		//Feb 19 00:21:40 iPad-test mb.iOS(libxamarin-debug.dylib)[2267] <Notice>: cancel request, -2126628254, already get, 26805062,
		//Feb 19 00:21:40 iPad-test mb.iOS[2267] <Notice>: cancel request, -2126628254, already get, 26805062,
		//** release版所有console.write没有(libxxx)
		//从idevicesyslog得到的消息已经切成多行，无法按程序或模块过滤，所以这里用awk按模式切分，取行首日期时间字段
		var pattern = '.';
		if (vl >= 1) {
			pattern = `${processName}`;
		}
		if (vl >= 2) {
			pattern = `${processName}(\\(libxamarin.*\\))?\\[`;
		}
		cmd = 'idevicesyslog';
		cmd += ` | awk 'BEGIN { p=0;} { 
			if($0~/^[A-z]{3} [ 0-9]{1,2} [0-9]{2}:[0-9]{2}:[0-9]{2}/) { 
				if($0~/${pattern}/){
					p=1;print $0;
					fflush();
				}
				else{
					p=0;
				}
			} else {
				 if(p==1){
 					print $0;
					fflush();
				 }
			}
		}'`;
		//c stack
		if (!argv.cs) cmd += ' | grep --line-buffered -v -E -e critical';

		//命令太复杂了，各种引号转义问题，只能先存成脚本文件再重定向输入
		if (ssh) {
			let sfn = '/tmp/ios-log.sh';
			fs.writeFileSync(sfn, cmd);
			//这个idevicesyslog必须要stdin为term，在本机重定向输入都不行，只能把脚本先拷到mac上再以bash命令运行它才行了
			shell('.', `scp ${sfn} ${config.vmac}:ios-log.sh`);
			cmd = `${ssh} bash ios-log.sh`;
		}

		//color
		if (argv.co) cmd += ' | grep --color -E "STDOUT|$" --line-buffered ';
		shell('.', cmd);
	}
};

module.exports.ios_stop = function() {
	try {
		var ssh = config.vmac ? `ssh ${config.vmac} ` : '';
		if (argv.sim) {
			shell('.', `${ssh} xcrun simctl terminate "${iosDevice}" ${iosAppId}`);
		} else {
			var bundle = config.vmac ? iosBuildRemotePath : iosBuildPath;
			shell('.', `${ssh} ios-deploy2 -L -S -m --bundle ${bundle}`);
		}
	} catch (e) {
		console.log('ios_stop', e);
	}
};

module.exports.ios_start = async function() {
	selectIOSDevice();

	if (config.iosBuildConfiguration == 'release') argv.r = true;

	if (argv.sim) {
		iosBuildPath = iosBuildPath.replace('iPhone', 'iPhoneSimulator');
	} else {
		argv.r = true;
	}

	if (argv.r) {
		iosBuildPath = iosBuildPath.replace('Debug', 'Release');
	}

	if (config.vmac) {
		var s = iosBuildPath.replace(/\\/g, '/').split('mb.iOS/')[1];
		iosBuildRemotePath = `./Library/Caches/Xamarin/mtbs/builds/mb.iOS/${config.vmacBuild}/${s}`;
		console.log(iosBuildPath, iosBuildRemotePath);
		// process.exit(1);
	}

	var ilrArg = {};
	if (argv.ilrgen) {
		var hotProjectFile = '../libmp.iOS/libmp.iOS.csproj';
		var hotfixConfiguration = config.hotfixConfiguration || 'Release'; //热更代码强制用release
		var hotDllPath = path.dirname(hotProjectFile) + `/bin/${hotfixConfiguration}/libmp.dll`;
		ilrArg = {
			ilrgen: true,
			mainDllPath: iosBuildPath + '/mb.iOS.exe',
			hotDllPath: path.resolve(hotDllPath),
			tplRootPath: path.resolve('./sbn'),
			genRootPath: path.resolve('../mb.iOS/gen'),
		};
		//删除所有typebind
		shell('.', `rm -rf ${ilrArg.genRootPath}/ilrbind/typebind`);
		await module.exports.refreshMainCSProject();
		// process.exit(1);
	}

	if (argv.b) {
		console.log('build ios...');
		await module.exports.buildCS('ios');
	}

	let stop = argv.s;
	var args = {
		// reportlog: ip + ':9283',
		autoserver: getAutoServer(),
		...ilrArg,
	};
	var ptk = 'pt-';
	for (var k in config) {
		console.log('check key', k);
		if (k.startsWith(ptk)) {
			args[k.substr(ptk.length)] = config[k];
		}
	}
	console.log('start ios', args);
	var aec = Buffer.from(JSON.stringify(args)).toString('base64');

	if (argv.l) {
		//强制本地
		config.vmac = undefined;
	}

	if (argv.sim) {
		module.exports.ios_stop();
		await doIfTimeNewer(iosBuildPath, undefined, [], ['bin', 'obj'], () => {
			if (config.vmac) {
				console.log('!!!!!!!!!!!!!!!!', iosBuildRemotePath);
				shell('./', `ssh ${config.vmac} "xcrun simctl install "${iosDevice}" ${iosBuildRemotePath}"`);
			} else {
				shell('./', `xcrun simctl install "${iosDevice}" ${iosBuildPath}`);
			}
		});
		if (config.vmac) {
			shell('./', `ssh ${config.vmac} "xcrun simctl launch "${iosDevice}" ${iosAppId} ${aec}"`);
		} else {
			shell('./', `xcrun simctl launch "${iosDevice}" ${iosAppId} ${aec}`);
		}
		if (config.autoActiveSim) {
			shell('./', 'open /Applications/Utilities/Terminal.app');
			shell('./', `open ${iosSimApp}`);
		}
		if (config.autoActiveSim == 2) {
			shell('./', 'open "/Applications/Visual Studio Code.app"');
		}

		if (argv.ilrgen) {
			let watch = require('node-watch');
			let wo, tm;
			wo = watch([ilrArg.genRootPath], { recursive: true }, (evt, name) => {
				console.log('ilrgen filechange', name);
				if (tm) clearTimeout(tm);
				tm = setTimeout(() => {
					wo.close();
					module.exports.refreshMainCSProject();
				}, 500);
			});
		}
	} else {
		var ssh = config.vmac ? `ssh ${config.vmac} ` : '';
		var bundle = config.vmac ? iosBuildRemotePath : iosBuildPath;
		var needInstall = false;
		await doIfTimeNewer(iosBuildPath, undefined, [], ['bin', 'obj'], () => {
			needInstall = true;
		});
		needInstall = true;
		if (!needInstall) module.exports.ios_stop();
		var installFlag = needInstall ? '' : '-m';
		shell('.', `${ssh} ios-deploy2 --no-wifi -L -a ${aec} ${installFlag} --bundle ${bundle}`);
	}
};

module.exports.ipa = function() {
	var sign = 'DEVELOPMENT_TEAM="J73CWRCUAR"';
	iosBuildPath = iosBuildPath.replace('Debug', 'Release');

	if (argv.icon) {
		let text = fs.readFileSync(`${xprjb}/Info.plist`).toString();
		let m = /CFBundleShortVersionString.+\n.+string>(.+)<\/string>/m.exec(text);
		let version = m[1];

		//更新图标上的版本号
		let isRD = false;
		let fx = isRD ? '-channel rgb -negate' : ''; //测试版给个反色
		['Icon-iPad-76', 'Icon-iPad-152', 'Icon-iPad-167'].map(v => {
			let iconPath = `${xprjb}/Unity-iPhone/Images.xcassets/AppIcon.appiconset/${v}.png`;
			let size = v * 2;
			let cmd = `convert ${iconPath} ${fx} -pointsize 24 -fill '#aa00aaff' -undercolor '#000000c0' -gravity SouthEast -annotate +30+0 ${version} ${iconPath}`;
			// `convert 1024.png -resize ${size}x${size} ${fx} -pointsize 24 -fill '#aaaaaaff' -undercolor '#000000c0' -gravity SouthEast -annotate +30+0 ${version} ${iconPath}`
			shell('./', cmd);
		});
		// shell('./', `convert 1024.png ${fx} -resize 512x512 -pointsize 70 -fill '#aaaaaaff' -undercolor '#000000c0' -gravity SouthEast -annotate +0+0 ${version} ios/iTunesArtwork`);
	}

	//打包
	if (argv.p) {
		shell('./', `rm -rf ${outputIPA}; xcrun -sdk iphoneos PackageApplication -v ${iosBuildPath} -o ${outputIPA} --embed mmd-dev`);
	}
};

module.exports.inc = function() {
	incVersionInPackage();
};

module.exports.ssl = function() {
	var os = require('os');
	var ifaces = os.networkInterfaces();
	let forceRegen = argv.f;

	var ipList = getLocalIP();
	var useIp = ipList[0];
	if (ipList.length > 1) {
		//select one
	}
	// useIp = 'mb.jj.com';

	var dataPath = path.resolve('../build/ssl');
	if (!fs.existsSync(dataPath)) fs.mkdirSync(dataPath);

	var caKey = `${dataPath}/ca-key.pem`;
	if (!fs.existsSync(caKey) || forceRegen) {
		if (!fs.existsSync(caKey)) {
			shell(dataPath, 'openssl genrsa -out ca-key.pem 1024');
			shell(dataPath, 'openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -subj \'/CN=Fuck/O=jj\' -out ca.pem');
		}
		shell(dataPath, 'openssl x509 -outform der -in ca.pem -out ca.crt');

		shell(dataPath, 'openssl genrsa -out server-key.pem 1024');
		fs.writeFileSync(dataPath + '/san', `subjectAltName=DNS:localhost,IP:${useIp}\nextendedKeyUsage=serverAuth`);
		shell(dataPath, `openssl req -subj '/CN=${useIp}/O=xjh' -sha256 -new -key server-key.pem -out server.csr `);

		shell(dataPath, 'openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile san');
		shell(dataPath, 'openssl x509 -in server-cert.pem -text');

		//copy to devs
		shell('.', `cp -r ${dataPath}/* ../devs/ssl/`);
		//copy to client
		shell('.', `cp -r ${dataPath}/ca.pem ../build/assets/`);
	}
};

module.exports.refreshMainCSProject = async function() {
	var content = fs.readFileSync(iosMainProjectFile).toString();

	var tag1 = '<!--ILRGEN START-->';
	var tag2 = '<!--ILRGEN END-->';

	content = content.substring(0, content.indexOf(tag1)) + '@@@' + content.substring(content.indexOf(tag2) + tag2.length);
	// console.log(content);

	var srcList = await getAllFiles('../mb.iOS/gen', [/_new/]);
	var snippet = srcList
		.map(v => {
			return `<Compile Include="${v.path.replace(/\//g, '\\')}"/>`;
		})
		.join('\n');

	console.log(snippet);
	content = content.replace('@@@', tag1 + '\n' + snippet + '\n' + tag2);
	fs.writeFileSync(iosMainProjectFile, content);
};

module.exports.buildXib = async function() {
	var cmd = [
		'/Applications/Xcode.app/Contents/Developer/usr/bin/ibtool',
		'--output-format human-readable-text',
		'--errors --warnings --notices --minimum-deployment-target 8.0 --target-device iphone --target-device ipad ',
		'--auto-activate-custom-fonts --sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.1.sdk ',
		'--compilation-directory ../build/assets/res ',
		// "/Users/wellbye/git-repo/j/mb/iOS/Src/ViewControllers/Entry.xib"
	].join(' ');
	var files = await getAllFiles('/Users/wellbye/git-repo/test/swtest/abc/abc', [/^(?!.*.xib)/]);
	for (const v of files) {
		console.log(v.path);
		shell('.', cmd + v.path);
	}
};

module.exports.buildLinkerTip = async function(targetPlatform) {
	shell('..', 'rm -rf libema && cp -r libmp.Android libema');
	var files = await getAllFiles('../libema', [/^(?!.*\.cs$)/]);
	console.log(files);
};

module.exports.buildCS = async function(targetPlatform) {
	targetPlatform = targetPlatform || argv.t;
	var platform = os.platform();
	var msbuild, cfp;
	if (platform == 'win32' || isWSL) {
		msbuild = 'MSBuild.exe';
		cfp = '/';
	} else {
		msbuild = 'msbuild';
		cfp = '/';
	}

	var Configuration = argv.r ? 'Release' : 'Debug';

	var Platform = switchcase({
		android: 'AnyCPU',
		ios: argv.sim ? 'iPhoneSimulator' : 'iPhone',
	})(targetPlatform);

	var mainProjectFile = targetPlatform == 'android' ? '../mb.Droid/mb.Droid.csproj' : '../mb.iOS/mb.iOS.csproj';

	var hotProjectFile = targetPlatform == 'android' ? '../libmp.Android/libmp.Android.csproj' : '../libmp.iOS/libmp.iOS.csproj';
	var hotfixConfiguration = config.hotfixConfiguration || 'Release'; //热更代码强制用release
	var hotDllPath = path.dirname(hotProjectFile) + `/bin/${hotfixConfiguration}/libmp.dll`;

	var extraParam = '';

	if (targetPlatform == 'ios') {
		let action = config.iosRebuild ? 'rebuild' : 'build';
		let remoteBuild = '';
		let codeSign = '';
		if (config.vmac) {
			// action = '_RemoteBuild';
			remoteBuild = ` /p:ServerAddress=${config.vmacServer} /p:ServerUser=${config.vmacUser} `;
			codeSign = ' /p:CodesignKey="iPhone Developer" /p:CodesignProvision=mmd-dev';
		}
		await doIfTimeNewer(['../mb.iOS', '../mb.Shared'], iosBuildPath, [], ['bin', 'obj'], () => {
			// console.log('!!!!!!!!!!!!!!!!!!!!!');
			// process.exit(1);
			shell('.', `${msbuild} /t:${action} ${cfp}p:Configuration=${Configuration} ${cfp}p:Platform=${Platform} ${remoteBuild} ${codeSign} ${mainProjectFile} ${extraParam}`);
		});
	}

	if (targetPlatform == 'android') {
		var mainDllPath = path.dirname(mainProjectFile) + `/bin/${Configuration}/mb.Droid.dll`;
		extraParam = cfp + 'p:OutputDirectory=./';
		var action = 'SignAndroidPackage';
		if (argv.m) {
			action = 'build';
		} else if (config.androidRebuild) {
			action = 'rebuild,' + action;
			shell('.', 'touch ../mb.Droid/hold.cs');
			shell('.', 'touch ../libmp.Android/src/Tool.cs');
		}

		var csprojContent = fs.readFileSync(mainProjectFile);
		androidUseFastDeploy = csprojContent.indexOf('<AndroidUseSharedRuntime>true</AndroidUseSharedRuntime>') > 0 && csprojContent.indexOf('<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>') < 0;
		// console.log('fast', androidUseFastDeploy);
		// process.exit();

		//默认检查apk文件，如果源代码比apk新，就要编译；但是在debug&&fastdeploy时，apk不会更新，所以直接以输出的dll文件为对比目标
		var dstPath = androidBuidPath;
		if (!argv.r && androidUseFastDeploy) {
			action = 'install';
			if (config.androidRebuild) action = 'rebuild,' + action;
			extraParam = cfp + `p:AdbTarget="-s ${androidDevice}"`;
			dstPath = mainDllPath;
		}

		var cmd = `${msbuild} /t:${action} ${cfp}p:Configuration=${Configuration} ${cfp}p:Platform=${Platform} ${mainProjectFile} ${extraParam} `;

		await doIfTimeNewer(['../mb.Droid', '../mb.Shared', '../mb.DroidLib'], dstPath, [], ['bin', 'obj'], () => {
			shell('.', cmd);
		});
	}

	//只编主工程
	if (argv.m) return;
	// if (argv.ilrgen) return;

	dstPath = path.dirname(hotProjectFile) + `/bin/${hotfixConfiguration}/libmp.dll`;
	var execed = await doIfTimeNewer(['../libmp.Shared', '../libmp.iOS', '../libmp.Android', '../common/rpc_gen/rpc_cs'], dstPath, [], ['bin', 'obj'], () => {
		action = config.androidRebuild ? 'rebuild' : 'build';
		shell('.', `${msbuild} /t:${action} ${cfp}p:Configuration=${hotfixConfiguration} ${hotProjectFile} ${extraParam}`);
		['libmp.dll', 'libmp.pdb'].forEach(n => {
			var src = `${path.dirname(hotProjectFile)}/bin/${hotfixConfiguration}/${n}`;
			var dst = `../build/assets/${n}.${targetPlatform}`;
			console.log('copy hotfix to bundle', src, dst);
			fs.copyFileSync(src, dst);
		});
	});

	//等1秒，主要防止热更服务器还没来得及刷新素材表
	if (execed) await new Promise(resolve => setTimeout(resolve, 1000));
};

module.exports.ilrGenerateInterfaceBinding = async function() {
	module.exports.ios_start();
};

module.exports.git_save_rb = async function() {
	var cmd = `git stash save\
			&& git co rb\
			&& git co stash -- .\
			&& (git ca -m "fix"||echo a)\
			&& git co master\
			&& echo "back to master"\
			&& git stash pop
	`;
	shell('.', cmd);

	cmd = `scp tool/localconfig.js ${config.vmac}:repos/mb/tool/`;
	shell('.', cmd);
	cmd = `ssh ${config.vmac} ". .bash_profile; cd repos/mb/tool; git pull; load-nvm; node npm-cmd ios_start -lbsm --sim"`;
	shell('.', cmd);
};

module.exports.redis = async function() {
	if (argv._.length == 1) {
		console.log(`
	usage:
	node npm-cmd.js redis [cmd] [option]

	cmd:
		init: 初始化数据库（在本机）
		web: 运行web ui
	
	option:
		`);
		return;
	}

	var cmd = argv._[1];
	if (cmd == 'init') {
		redisInit();
	} else if (cmd == 'web') {
		redisWeb();
	}
};

module.exports.docker = async function() {
	if (argv._.length == 1) {
		console.log(`
	usage:
	node npm-cmd.js docker [cmd] [option]

	cmd:
		build: 构建image
		deploy: 通过模板生成最终docker-deploy.yml进行部署
		export: 导出
	
	option:
		-p: 推送，build成功后把image推送到远程仓库
		--res: 同时build res image，如果有-p也一起推送

		-b: 用于deploy命令，在部署前先运行构建，当运行构建时上面的参数都一样支持
		-t: 测试，在本地构建启动，image名字不带域名；否则使用远程仓库域名构建
		`);
		return;
	}

	var cmd = argv._[1];
	if (cmd == 'build') {
		dockerBuild();
	} else if (cmd == 'deploy') {
		dockerDeploy();
	} else if (cmd == 'login') {
		cmd = `docker login ${dockerRegistry} -u ${argv.u} -p ${argv.p}`;
		if (argv.s) shell('.', `ssh ${argv.s} ${cmd}`);
		else shell('.', cmd);
	} else if (cmd == 'export') {
		dockerExport();
	}
};

module.exports.ssh = async function() {
	var deployCfg = require('./deploy');
	var serverCfg = deployCfg[argv.s];
	if (argv.e) console.log(serverCfg.ssh);
	else shell('./', `ssh ${serverCfg.ssh}`);
};

module.exports.sshpf = async function() {
	var Client = require('ssh2').Client;
	const Socket = require('net').Socket;
	var conn = new Client();
	var remotePort = 8081;
	var remoteIP = '18.138.136.9';
	var remoteUser = 'ec2-user';
	conn
		.on('error', function(e) {
			console.log('ssh reverse forward error', e);
		})
		.on('ready', function() {
			console.log('Client :: ready');
			conn.forwardIn('0.0.0.0', remotePort, function(err) {
				if (err) {
					// errors.push(err);
					throw err;
				}
				console.log('Listening for connections on server on port remotePort!');
			});
		})
		.on('tcp connection', function(info, accept, reject) {
			console.log('TCP :: INCOMING CONNECTION:');
			// console.dir(info);

			var srcSocket = new Socket();

			srcSocket.connect(remotePort, 'localhost', function() {
				var remote = accept();
				console.log('accept remote connection');
				srcSocket.pipe(remote).pipe(srcSocket);
			});
		})
		.connect({
			host: remoteIP,
			port: 22,
			username: remoteUser,
			privateKey: fs.readFileSync('./sh-key'),
		});
};

module.exports.pack_smgr = async function() {
	//
	let nccOutDir = 'build/ncc';
	let nexeOut = 'build/nsm';
	if (!fs.existsSync(nccOutDir)) fs.mkdirSync(nccOutDir, { recursive: true });
	let cmd;
	//compile typescript
	cmd = 'tsc';
	shell('.', cmd);
	//ncc webpack
	cmd = 'yarn ncc build dist/smgr.js -o build/ncc';
	shell('.', cmd);
	//nexe
	cmd = 'yarn nexe -i build/ncc/index.js -r build/ncc -o build/nsm';
	shell('.', cmd);
};

module.exports.pto = async function() {
	let protoDir = '../common/proto';
	let protoDef = 'user.proto';
	let home = process.env.HOME;
	let cmd;

	//生成js pbo(google-protobuf)
	if (argv.gg) {
		let pluginTS = path.resolve('../devs/node_modules/.bin/protoc-gen-ts');
		cmd = `protoc --plugin="protoc-gen-ts=${pluginTS}" --js_out="import_style=commonjs,binary:../../devs/gen/pbo" --ts_out="service=false:../../devs/gen/pbo" ${protoDef}`;
		shell(protoDir, cmd);
	}

	//生成js pbo(protobufjs)
	{
		let outputDir = '../devs/gen';
		let outputTS = `${outputDir}/pb.d.ts`;
		let cmd = `pbjs -t static-module -w commonjs ${protoDir}/${protoDef} > ${outputDir}/pb.js`;
		shell('.', cmd);
		cmd = `pbts -t static ${outputDir}/pb.js --force-message -o ${outputTS}`;
		shell('.', cmd);

		//public adImages?: mb.IAdImage[];
		//public audioCanPlay?: (mb.ICountPair|null); 把实现类的属性?去掉，前面有public为标记（interface里没有）
		replace({ paths: [outputTS], regex: /(public [A-z]+)\?:/g, replacement: (m, p1, p2) => `${p1}:` });
		//public audioCanPlay: (mb.ICountPair|null); 把实现类属性的类型也换成实现类(因为只有实现类才没有可选属性，符合grpc标准)
		replace({ paths: [outputTS], regex: /(public.*mb\.)I(.+)/g, replacement: (m, p1, p2) => `${p1}${p2}` });

		//生成js rpc
		cmd = `yarn grpc-code-generator ${protoDir}/* -o ${outputDir}/rpc`;
		shell('.', cmd);

		//把proto文件也复制过去，创建grpc server时要用
		shell('.', `cp ${protoDir}/*.proto ../devs/swagger/`);
	}

	//生成cs rpc
	let protoc = `${home}/.nuget/packages/grpc.tools/1.20.1/tools/macosx_x64/protoc`;
	let myprotoc = 'thirdparty/protoc';
	let grpc = `${home}/.nuget/packages/grpc.tools/1.20.1/tools/macosx_x64/grpc_csharp_plugin`;
	cmd = `${protoc} --csharp_out=./common/rpc_gen/rpc_cs2 --grpc_out=./common/rpc_gen/rpc_cs2 --plugin=protoc-gen-grpc=${grpc}  ./common/proto/user.proto`;
	shell('..', cmd);
	cmd = `${myprotoc} --csharp_out=./common/rpc_gen/rpc_cs --grpc_out=./common/rpc_gen/rpc_cs --plugin=protoc-gen-grpc=${grpc}  ./common/proto/user.proto`;
	shell('..', cmd);
	let cslist = (await getAllFiles('../common/rpc_gen/rpc_cs')).map(v => v.path);
	console.log('cslist', cslist);
	//去掉reflection部份
	replace({
		paths: cslist,
		regex: /.*Protobuf\.Reflection.*\n.*\n.*\n/gm,
		replacement: (m, p1, p2) => '//',
	});
	//去掉泛型基类
	replace({
		paths: cslist,
		regex: /(grpc::ClientBase)<.*>/g,
		replacement: (m, p1, p2) => p1,
	});
	replace({
		paths: cslist,
		regex: /.*NewInstance(.*\n){3}/gm,
		replacement: () => '//',
	});
};

module.exports.telegram = async function() {
	var telegram = require('telegram-bot-api');
	var token = process.env.TELEGRAM_TOKEN;
	console.log('bot', token);
	if (!token) {
		console.log('还未设置bot token');
		return;
	}

	var api = new telegram({
		token,
		updates: { enabled: argv.u },
		http_proxy: {
			host: 'localhost',
			port: 9527,
		},
	});

	let ab = 873621998;
	let wxr = 837121324;
	let gid = -284071375;

	api.on('update', function(message) {
		console.log('update', message);
	});

	api.getMe().then(function(data) {
		console.log(data);
	});

	var agent = 'https_proxy=localhost:9527';
	let url = `https://api.telegram.org/bot${token}/senddocument`;
	if (argv.uploadapk) {
		api.sendMessage({ chat_id: gid, text: '即将上传apk' });
		await sleep(1000);
		androidBuidPath = androidBuidPath.replace('Debug', 'Release');
		let document = androidBuidPath;
		let cmd = `${agent} curl --progress-bar -o /tmp/send.txt -F chat_id="${gid}" -F document=@"${document}" ${url}`;
		shell('.', cmd);
	}
	if (argv.uploadimage) {
		let outputDir = '/tmp/mbdi';
		if (!argv.skipsave) {
			shell('.', `rm -rf ${outputDir} && mkdir ${outputDir}`);
			shell('.', `docker save mb-api -o ${outputDir}/mb-api`);
			shell('.', `docker save mb-res -o ${outputDir}/mb-res`);
		}

		let zipFileP = '/tmp/mbdiz';
		if (!argv.skipzip) {
			shell('.', `rm -rf ${zipFileP}*`);
			shell('.', `zip -r -s 30m ${zipFileP}.zip ${outputDir}`);
		}
		let n = shell('.', `ls -l ${zipFileP}.z*|wc -l`, { stdio: '' });

		console.log('zip count', n);
		try {
			api.sendMessage({ chat_id: gid, text: `即将上传镜像，共有${n}个分卷` });
			await sleep(1000);
			let count = parseInt(n);
			for (var i = 1; i <= count; i++) {
				let document = zipFileP + '.z' + (i < count ? i.toString().padStart(2, 0) : 'ip');
				let cmd = `${agent} curl --progress-bar -o /tmp/send.txt -F chat_id="${gid}" -F document=@"${document}" ${url}`;
				// console.log(cmd);
				shell('.', cmd);
			}
			api.sendMessage({ chat_id: gid, text: '镜像上传完成' });
		} catch (e) {
			console.log('send fail', e.message);
		}
	}
};

module.exports[cmd]();

async function redisInit() {
	shell('.', 'docker run -d -p 0.0.0.0:6379:6379 --restart=unless-stopped --name redis redis');
}

async function redisWeb() {
	var hosts = config.redisWebWatchingHost || [];
	shell('.', 'docker rm -f rediscommander', { noerror: true });
	shell('.', `docker run -d -p 8081:8081 --restart=unless-stopped --name rediscommander --env REDIS_HOSTS="${hosts.join(',')}" rediscommander/redis-commander:latest`);

	//docker ui
	// docker run -d -p 9000:9000 --restart always -v /var/run/docker.sock:/var/run/docker.sock -v /opt/portainer:/data portainer/portainer
}

//打包资源image，因为开发时资源目录是符号链接了多处资源到一起，dockerfile不支持符号链接
//这里用rsync先把所有资源复制到一个统一地方再开始build
async function dockerBuildRes() {
	var image = 'node:alpine';
	var output = `
		FROM ${image}
		RUN mkdir -p /assets
		COPY . /assets
	`;
	var workDir = process.env.HOME + `/tmp/${projectName}-res-image/`;
	console.log('iswsl', isWSL);
	if (isWSL) {
		workDir = `/mnt/d/tmp/${projectName}-res-image/`;
	}
	if (!fs.existsSync(workDir)) fs.mkdirSync(workDir, { recursive: true });

	shell('../build/assets', `rsync -vrmLP --checksum --delete ./ ${workDir}`);
	fs.writeFileSync(`${workDir}/Dockerfile`, output);
	shell(workDir, `docker build -t ${dockerResImageName} -t ${dockerResImageUrl} .`);

	if (argv.p) {
		cmd = `docker push ${dockerImageUrl.replace('api', 'res')}`;
		shell('.', cmd);
	}
}

async function dockerBuild() {
	var tb = argv.t ? 1 : 0;
	tb = 0;
	var cmd = `docker build -t ${dockerImageName} -t ${dockerImageUrl}\
				--build-arg TESTBUILD=${tb}\
	 			.`;
	shell('../devs', cmd);

	if (argv.p) {
		cmd = `docker push ${dockerImageUrl}`;
		shell('.', cmd);
	}

	if (argv.res) {
		dockerBuildRes();
	}
}

async function dockerDeploy() {
	if (argv.b) {
		if (!argv.t) {
			// console.log('远程部署不支持 -b (同时build)，请先运行build并推送image后再部署！');
			// return;
			argv.p = true;
		}
		await dockerBuild();
	}
	if (!argv.role) {
		console.log('必须指定部署角色--role=user/sync');
		return;
	}

	if (argv.p) {
		cmd = `docker push ${dockerImageUrl}`;
		shell('.', cmd);
		if (argv.res) {
			cmd = `docker push ${dockerResImageUrl}`;
			shell('.', cmd);
		}
	}

	var deployCfg = require('./deploy');
	var server = argv.s;
	var serverCfg = server && deployCfg[server];

	//本机测试时，使用特殊名字的服务器短名
	if (!serverCfg && argv.t) {
		var sc1 = deployCfg[os.hostname()];
		var sc2 = deployCfg['local'];
		serverCfg = sc1 || sc2 || {};
	}

	if (!serverCfg) {
		console.log('没有找到远程部署的服务器配置，请定义好配置再部署', server);
		return;
	}
	if (!serverCfg.SyncRedis && argv.role == 'user') {
		console.log('用户服务器未指定同步源, 默认连接本机转发端口，请注意建立转发', server);
		// return;
	}

	var template = fs.readFileSync('dc-deploy.template').toString();
	var output = mustache.render(template, {
		imageApi: argv.t ? dockerImageName : dockerImageUrl,
		imageRes: argv.t ? dockerResImageName : dockerResImageUrl,
		DOCKER_HOST: serverCfg.DOCKER_HOST || '172.17.0.1',
		UserServer: argv.role == 'user',
		SyncServer: argv.role == 'sync',
		SyncRedis: serverCfg.SyncRedis,
	});
	fs.writeFileSync('docker-compose.yml', output);
	if (argv.role == 'user') {
		fs.writeFileSync('../devs/html/mb-user.dc', output);
	} else if (argv.role == 'sync' && !argv.t) {
		fs.writeFileSync('../devs/html/mb-sync.dc', output);
	}

	let dcpName = `${projectName}-${argv.role}`;
	if (argv.stop) {
		shell('.', `docker-compose -p ${dcpName} down`);
	} else if (argv.t) {
		//-v参数用来删除volume，确保每次都用到最新的res image
		//不能用-v了，会把数据库volume也删掉，这里手动删除assets volume
		shell('.', `docker-compose -p ${dcpName} down`);
		shell('.', `docker volume rm ${dcpName}_assets`, { noerror: true });
		shell('.', `docker-compose -p ${dcpName} up -d`);
	} else {
		var dcfPath = dcpName + '.dc';
		var host = serverCfg.ssh || server;
		cmd = `
			scp docker-compose.yml ${host}:${dcfPath}
			ssh ${host} "docker-compose -f ${dcfPath} pull"\\ &&
			ssh ${host} "docker-compose -p ${dcpName} -f ${dcfPath} down"\\ &&
			ssh ${host} "docker volume rm -f ${dcpName}_assets"\\ &&
			ssh ${host} "docker-compose -p ${dcpName} -f ${dcfPath} up -d"
		`;
		shell('.', cmd);
	}
}

async function dockerExport() {
	console.log('docker export');
	shell('.', 'docker save mb-api -o /mnt/d/mb-api');
	shell('.', 'docker save mb-res -o /mnt/d/mb-res');
}

function incVersionInPackage() {
	replace({
		regex: /("version": ".*\.)(\d*)/,
		replacement: function(m, p1, p2) {
			return p1 + (parseInt(p2) + 1);
		},
		paths: ['package.json'],
	});
}

async function getAllFiles(dir, fileFilters = [], dirFilters = []) {
	let files = [];
	let walk = require('walk').walk;
	await new Promise(fill => {
		console.log('walk', dir, dirFilters);
		let walker = walk(dir, {
			followLinks: true,
			filters: dirFilters,
		});
		walker.on('file', (root, fileStats, next) => {
			// console.log('file', root, fileStats.name);
			root = root.replace(/\\/g, '/'); // for windows
			let path = root + '/' + fileStats.name;
			for (let r of fileFilters) {
				if (r.test(path)) return next();
			}
			files.push({
				path: root + '/' + fileStats.name,
				size: fileStats.size,
				mtimeMs: fileStats.mtimeMs,
			});
			next();
		});
		walker.on('end', fill);
	});
	return files;
}

async function getDirectoryModifyTimeRecursive(dir, fileFilter, dirFilter) {
	var files = await getAllFiles(dir, fileFilter, dirFilter);
	var time = 0;
	var maxTimePath = '';
	files.forEach(v => {
		// console.log(v.path, new Date(v.mtimeMs).toLocaleString());
		if (v.mtimeMs > time) {
			time = v.mtimeMs;
			maxTimePath = v.path;
		}
	});
	console.log('max time', dir, new Date(time).toLocaleString(), maxTimePath);
	return time;
}

async function doIfTimeNewer(srcPath, dstPath, fileFilter, dirFilter, handler) {
	var srcList = [srcPath];
	if (typeof srcPath == 'object') {
		srcList = srcPath;
		srcPath = srcPath[0];
	}

	var dstIsFlag = false;
	if (dstPath == undefined || typeof dstPath == 'object') {
		var flagPath =
			'/tmp/stamp-' +
			path
				.resolve(srcPath)
				.replace(/\./g, '-')
				.replace(/:/g, '-')
				.replace(/\\/g, '-')
				.replace(/\//g, '-');

		if (dstPath && dstPath.suffix) flagPath += dstPath.suffix;
		dstPath = flagPath;
		dstIsFlag = true;
	}

	console.log('doIfTimeNewer', srcPath, dstPath);
	var srcTimes = srcList.map(p => {
		if (!fs.existsSync(p)) return 0;

		var st = fs.statSync(p);
		if (st.isFile()) return st.mtimeMs;
		else if (st.isDirectory()) return getDirectoryModifyTimeRecursive(p, fileFilter, dirFilter);
		else return 0;
	});
	srcTimes = await Promise.all(srcTimes);

	var srcMaxTime = Math.max(...srcTimes);
	var dstTime = fs.existsSync(dstPath) ? fs.statSync(dstPath).mtimeMs : 0;
	var needExec = srcMaxTime > dstTime;
	console.log(needExec ? chalk.red('need exec') : chalk.green('no need exec'));
	console.log('	src:', new Date(srcMaxTime).toLocaleString());
	console.log('	dst:', new Date(dstTime).toLocaleString());
	if (needExec) {
		await handler();
		//touch目标文件
		dstIsFlag && shell('.', `touch ${dstPath}`);
	}
	return needExec;
}
